﻿///<reference path="../../../Scripts/typings/jquery/jquery.d.ts"/>
///<reference path="../../UrlHelper.ts"/>
///<reference path="../../Models/BasketModel.ts"/>
///<reference path="../../Models/BasketItemModel.ts"/>
///<reference path="../../Models/PromotionApplicationResultModel.ts"/>
///<reference path="../../ViewModels/Interfaces/IBasketViewModel.ts"/>
///<reference path="../../ViewModels/Interfaces/IBasketItemViewModel.ts"/>

module Enferno.Public.Web.Components.Controllers.Base {

    export interface IOnBasketInitilizedFunction {
        (basketController: BasketControllerBase<ViewModels.Interfaces.IBasketViewModel, ViewModels.Interfaces.IBasketItemViewModel>): void;
    }

    export class BasketEvents {
        public static basketChanged: string = "Enferno.Basket.Events.BasketChanged";
    }

    export class BasketControllerBase<TBasketViewModel extends ViewModels.Interfaces.IBasketViewModel, TBasketItemViewModel extends ViewModels.Interfaces.IBasketItemViewModel> {

        public static onInitializedFunctions = new Array<IOnBasketInitilizedFunction>();

        public viewModel: TBasketViewModel;
        public getQuantityToAdd: () => number;
        public priceFormator: (price: number) => string;
        //functions the can be set and will be called upon different events.
        public onItemAdded: (itemAdded: Models.BasketItemModel) => void;
        public onPromotionApplied: (promotionApplicationResult: Models.PromotionApplicationResultModel) => void;
        

        public addtoBasketDomNodeSelector = ".add-to-basket";
        public toggleBasketDomNodeSelector = ".toggle-basket";

        public init(): JQueryPromise<void>;
        public init(basketModel: Models.BasketModel): JQueryPromise<void>;
        public init(basketModel?: Models.BasketModel): JQueryPromise<void> {
            var deferred = $.Deferred<void>();

            var loadBasketDeferred = $.Deferred<void>();

            if (basketModel != null) {
                this.viewModel = this.createViewModel(basketModel);
                loadBasketDeferred.resolve();
            } else {
                this.loadBasket().then(() => loadBasketDeferred.resolve());
            }

            //fetch the basket (or create a new on if none exists)
            loadBasketDeferred.promise().then(() => {

                Enumerable.from(BasketControllerBase.onInitializedFunctions)
                    .reverse()
                    .forEach((fn: IOnBasketInitilizedFunction) => fn(this));

                //set up bindings
                $(document).on("click", this.addtoBasketDomNodeSelector, this.onAddToBasketClicked);
                $(document).on("click", this.toggleBasketDomNodeSelector, this.onToggleBasketClicked);

                this.afterInitilized();

                deferred.resolve();
            });

            return deferred.promise();
        }

        private loadBasket(): JQueryPromise<void> {
            var deferred = $.Deferred<void>();

            $.get(UrlHelper.getUrl(BasketServices.GetBasket))
                .then(fetchedBasket => {
                    var basketModel = new Models.BasketModel(fetchedBasket);
                    this.viewModel = this.createViewModel(basketModel);
                    this.updateRegisterDiscountPromotionsFromPromotions();
                    deferred.resolve();
                });

            return deferred.promise();
        }

        public createViewModel(basketModel: Models.BasketModel): TBasketViewModel {
            throw "Class is abstract. Implement in concrete class";
        }

        public afterInitilized() {
            throw "Class is abstract. Implement in concrete class";
        }

        public getFormattedPriceString(price: number): string {
            return this.priceFormator ? this.priceFormator(price) : price.toString();
        }

        public disableEditability(): void {
            this.viewModel.setIsEditable(false);
        }

        public enableEditability(): void {
            this.viewModel.setIsEditable(true);
        }

        public onApplyDiscountClicked(discountCode: string): void {
            this.applyDiscount(discountCode);
        }

        public onRemovePromotionClicked(promotionId: number, resetDiscountCode: boolean): void {
            this.removePromotion(promotionId, resetDiscountCode);
        }

        public open() {
            this.viewModel.setIsOpen(true);
        }

        public close() {
            this.viewModel.setIsOpen(false);
        }

        public getTotalSum(): number {
            var sum = Enumerable.from(this.viewModel.getItems()).sum((item: Models.BasketItemModel) => {
                return item.getPriceSum();
            });

            sum += this.getFreightSum();

            sum += this.getPaymentSum();

            return sum;

        }

        public getFreightSum(): number {
            var sum = Enumerable.from(this.viewModel.getFreights()).sum((freight: Models.BasketItemModel) => {
                return freight.getPriceSum();
            });

            return sum;
        }

        public getPaymentSum(): number {
            var sum = Enumerable.from(this.viewModel.getPayments()).sum((payment: Models.BasketItemModel) => {
                return payment.getPriceSum();
            });

            return sum;
        }

        public getFormattedSumString(sum: number) {
            if (this.priceFormator != null) {
                return this.priceFormator(sum);
            }
            return sum.toString();            
        }

        public getTotalSumFormattedString(): string {
            var totalSum = this.getTotalSum();
            if (this.priceFormator != null) {
                return this.priceFormator(totalSum);
            }
            return totalSum.toString();
        }

        public addItem(basketItemModel: Models.BasketItemModel) {
            var url = UrlHelper.getUrl(BasketServices.AddItem, [this.viewModel.getId()]);
            this.viewModel.setIsLoading(true);
            $.post(url, basketItemModel.serialize())
                .then(updatedBasket => {
                    this.setDataFromServer(updatedBasket);
                    if (this.onItemAdded != null) {
                        this.onItemAdded(basketItemModel);
                        $.Event(BasketEvents.basketChanged);
                    }
                })
                .always(() => {
                    this.viewModel.setIsLoading(false);
                });
        }

        public deleteItem = (basketItemModel: Models.BasketItemModel) => {
            var basket = this;
            var url = UrlHelper.getUrl(BasketServices.DeleteItem, [basket.viewModel.getId()]);
            this.viewModel.setIsLoading(true);
            $.post(url, { lineNo: basketItemModel.lineNo }).then(updatedBasket => {
                this.setDataFromServer(updatedBasket);
                $.Event(BasketEvents.basketChanged);
            })
            .always(() => {
                this.viewModel.setIsLoading(false);
            });
        }

        public updateItem(basketItemModel: Models.BasketItemModel) {
            var url = UrlHelper.getUrl(BasketServices.UpdateItem, [this.viewModel.getId()]);
            this.viewModel.setIsLoading(true);
            $.post(url, basketItemModel.serialize())
                .then(updatedBasket => {
                    this.setDataFromServer(updatedBasket);
                    $.Event(BasketEvents.basketChanged);
                })
                .always(() => {
                    this.viewModel.setIsLoading(false);
                });
        }

        public applyDiscount(discountCode: string) {
            var url = UrlHelper.getUrl(BasketServices.ApplyDiscount, [this.viewModel.getId()]);
            this.viewModel.setIsLoading(true);
            $.post(url, { discountCode: discountCode })
                .then(applyPromotionResult => {
                    var result = new Models.PromotionApplicationResultModel(applyPromotionResult);
                    this.setDataFromServer(applyPromotionResult.basket);
                    $.Event(BasketEvents.basketChanged);
                    if (this.onPromotionApplied != null) {
                        this.onPromotionApplied(result);
                    }
                })
                .always(() => {
                    this.viewModel.setIsLoading(false);
                });
        }

        public removePromotion(promotionId: number, resetDiscountCode: boolean) {
            var url = UrlHelper.getUrl(BasketServices.RemovePromotion, [this.viewModel.getId()]);
            this.viewModel.setIsLoading(true);
            $.post(url, { promotionId: promotionId, resetDiscountCode: resetDiscountCode })
                .then(updatedBasket => {
                    this.setDataFromServer(updatedBasket);
                    $.Event(BasketEvents.basketChanged);
                })
                .always(() => {
                    this.viewModel.setIsLoading(false);
                });
        }

        private setDataFromServer(serverObject: any) {
            var model = new Models.BasketModel(serverObject);
            this.viewModel.update(model, this.basketItemQuantityOnChange);
        }

        private updateRegisterDiscountPromotionsFromPromotions() {
            var discountPromotions = Enumerable.from(this.viewModel.getPromotions()).where(promotion => promotion.requirementSeed == "10" || promotion.requirementSeed == "22").toArray();
            this.viewModel.clearRegisteredDiscountPromotions();
            Enumerable.from(discountPromotions).forEach(discountPromotion => this.viewModel.addRegisteredDiscountPromotion(discountPromotion));
        }

        public basketItemQuantityOnChange = (newQuantity: number, basketItemModel: Models.BasketItemModel): void => {
            this.updateItem(basketItemModel);
        }

        private onAddToBasketClicked = (eventObject: JQueryEventObject, args: any[]): any => {

            var partNo = $(eventObject.target).data("partno");
            var priceListId = $(eventObject.target).data("pricelistid");

            var quantity: number;

            if (this.getQuantityToAdd != null) {
                quantity = this.getQuantityToAdd();
            } else {
                quantity = $(eventObject.target).data("quantity") || 1;
            }

            var basketItemModel = Models.BasketItemModel.create(partNo, priceListId, quantity);
            this.addItem(basketItemModel);
            this.open();
        }

        private onToggleBasketClicked = (eventObject: JQueryEventObject, args: any[]): any => {
            if (this.viewModel.getIsOpen()) {
                this.close();
            } else {
                this.open();
            }
        }
    }

} 